package glossos.tree;

import java.util.*;

public class Node {
	private static Comparator<Node> frequencyComparator = new Comparator<Node>() {
		public int compare(Node o1, Node o2) {
			return o1.getFrequency().compareTo(o2.getFrequency());
		}
	};
	
	private Long id;
	private String word;
	private Integer frequency;
	private Map<String, Node> children;
	private Node parent;
	private Long parentId;

	public Node() {
		this("");
	}

	public Node(String word) {
		this(word, 0);
	}

	public Node(String word, Integer frequency) {
		this(0L, word, frequency);
	}

	public Node(Long id, String word, Integer frequency) {
		this.id = id;
		this.word = word;
		this.frequency = frequency;
		children = new HashMap<String, Node>();
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getWord() {
		return word;
	}

	public void setWord(String word) {
		this.word = word;
	}

	public Integer getFrequency() {
		return frequency;
	}

	public void setFrequency(Integer frequency) {
		this.frequency = frequency;
	}

	public Map<String, Node> getChildren() {
		return children;
	}

	public void setChildren(Map<String, Node> children) {
		this.children = children;
	}

	public Node getParent() {
		return parent;
	}

	public void setParent(Node parent) {
		this.parent = parent;
	}

	public boolean isRoot() {
		return parent == null;
	}

	public boolean isLeaf() {
		return children.isEmpty();
	}

	public Node childWithWord(final String word) {
		return children.get(word);
	}

	public boolean hasChildWithWord(final String word) {
		return children.containsKey(word);
	}

	public Node append(Node child) {
		return !hasChildWithWord(child.getWord())
				? insertChild(child)
				: incrementChildFrequency(child.getWord());
	}

	public Node incrementChildFrequency(String word) {
		Node node = childWithWord(word);
		node.frequency++;
		children.put(node.getWord(), node);
		return node;
	}

	private Node insertChild(Node child) {
		child.setParent(this);
		child.setFrequency(child.getFrequency() + 1);
		children.put(child.getWord(), child);
		return child;
	}

	public boolean lessFrequentThan(Node other) {
		return frequencyComparator.compare(this, other) < 0;
	}

	public String sequence() {
		return NodeStack.forNode(this).toString();
	}

	public String sequenceTail(Node from) {
		return NodeStack.forNodes(this, from).toString();
	}

	/**
	 * Returns the child of this node with maximum frequency.
	 * Obs.: this method is guaranteed to return a sensible value only if
	 * {@link #isLeaf() isLeaf} returns false;
	 * @return child with maximum frequency.
	 */
	public Node mostFrequentChild() {
		return Collections.max(children.values(), frequencyComparator);
	}

	@Override
	public String toString() {
		return id + ":" + word + "|" + frequency;
	}

	@Override
	public int hashCode() {
		return id.hashCode();
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		Node other = (Node) obj;
		return this.id.equals(other.id);
	}

	public Collection<Node> nodesForPattern(String[] pattern, int i) {
		if (i == pattern.length) {
			return Collections.singleton(this);
		}
		if (MatchPatternPredicate.isLiteral(pattern[i])) {
			return hasChildWithWord(pattern[i])
					? childWithWord(pattern[i]).nodesForPattern(pattern, i + 1)
					: new ArrayList<Node>(0);
		} else {
			List<Node> result = new ArrayList<Node>();
			for (Node child : children.values()) {
				result.addAll(child.nodesForPattern(pattern, i + 1));
			}
			return result;
		}
	}

	public Long getParentId() {
		if (parentId != null) {
			return parentId;
		}
		return getParent() != null ? getParent().getId() : null;
	}

	public void setParentId(Long id) {
		parentId = id;
	}
}
